Advanced Topic: Using External C++ Functions¶
This is based on the relevant portion of the CmdStan documentation here
Consider the following Stan model, based on the bernoulli example.
[2]:
from cmdstanpy import CmdStanModel
model_external = CmdStanModel(stan_file='bernoulli_external.stan', compile=False)
print(model_external.code())
functions {
real make_odds(real theta);
}
data {
int<lower=0> N;
array[N] int<lower=0, upper=1> y;
}
parameters {
real<lower=0, upper=1> theta;
}
model {
theta ~ beta(1, 1); // uniform prior on interval 0, 1
y ~ bernoulli(theta);
}
generated quantities {
real odds;
odds = make_odds(theta);
}
As you can see, it features a function declaration for make_odds
, but no definition. If we try to compile this, we will get an error.
[3]:
model_external.compile()
INFO:cmdstanpy:compiling stan file /home/docs/checkouts/readthedocs.org/user_builds/cmdstanpy/checkouts/v1.0.1/docsrc/examples/bernoulli_external.stan to exe file /home/docs/checkouts/readthedocs.org/user_builds/cmdstanpy/checkouts/v1.0.1/docsrc/examples/bernoulli_external
ERROR:cmdstanpy:Stan program failed to compile:
WARNING:cmdstanpy:
--- Translating Stan model to C++ code ---
bin/stanc --o=/home/docs/checkouts/readthedocs.org/user_builds/cmdstanpy/checkouts/v1.0.1/docsrc/examples/bernoulli_external.hpp /home/docs/checkouts/readthedocs.org/user_builds/cmdstanpy/checkouts/v1.0.1/docsrc/examples/bernoulli_external.stan
Semantic error in '/home/docs/checkouts/readthedocs.org/user_builds/cmdstanpy/checkouts/v1.0.1/docsrc/examples/bernoulli_external.stan', line 2, column 2 to column 29:
-------------------------------------------------
1: functions {
2: real make_odds(real theta);
^
3: }
4: data {
-------------------------------------------------
Some function is declared without specifying a definition.
make: *** [make/program:50: /home/docs/checkouts/readthedocs.org/user_builds/cmdstanpy/checkouts/v1.0.1/docsrc/examples/bernoulli_external.hpp] Error 1
Command ['make', '/home/docs/checkouts/readthedocs.org/user_builds/cmdstanpy/checkouts/v1.0.1/docsrc/examples/bernoulli_external']
error during processing No such file or directory
Even enabling the --allow-undefined
flag to stanc3 will not allow this model to be compiled quite yet.
[4]:
model_external.compile(stanc_options={'allow-undefined':True})
INFO:cmdstanpy:compiling stan file /home/docs/checkouts/readthedocs.org/user_builds/cmdstanpy/checkouts/v1.0.1/docsrc/examples/bernoulli_external.stan to exe file /home/docs/checkouts/readthedocs.org/user_builds/cmdstanpy/checkouts/v1.0.1/docsrc/examples/bernoulli_external
To resolve this, we need to both tell the Stan compiler an undefined function is okay and let C++ know what it should be.
We can provide a definition in a C++ header file by using the user_header
argument to either the CmdStanModel constructor or the compile
method.
This will enables the allow-undefined
flag automatically.
[5]:
model_external.compile(user_header='make_odds.hpp')
INFO:cmdstanpy:compiling stan file /home/docs/checkouts/readthedocs.org/user_builds/cmdstanpy/checkouts/v1.0.1/docsrc/examples/bernoulli_external.stan to exe file /home/docs/checkouts/readthedocs.org/user_builds/cmdstanpy/checkouts/v1.0.1/docsrc/examples/bernoulli_external
INFO:cmdstanpy:compiled model executable: /home/docs/checkouts/readthedocs.org/user_builds/cmdstanpy/checkouts/v1.0.1/docsrc/examples/bernoulli_external
We can then run this model and inspect the output
[6]:
fit = model_external.sample(data={'N':10, 'y':[0,1,0,0,0,0,0,0,0,1]})
fit.stan_variable('odds')
INFO:cmdstanpy:CmdStan start processing
INFO:cmdstanpy:CmdStan done processing.
[6]:
array([0.361904, 0.954058, 0.420802, ..., 0.581759, 0.42715 , 0.266583])
The contents of this header file are a bit complicated unless you are familiar with the C++ internals of Stan, so they are presented without comment:
#include <boost/math/tools/promotion.hpp>
#include <ostream>
namespace bernoulli_model_namespace {
template <typename T0__> inline typename
boost::math::tools::promote_args<T0__>::type
make_odds(const T0__& theta, std::ostream* pstream__) {
return theta / (1 - theta);
}
}